/*
C 头文件的包含顺序研究
https://blog.csdn.net/gdruhv/article/details/83747651

TODO C文件中的宏定义顺序会受顺序的影响吗，如果定义关联的宏定义会有什么后果？
    同样的定义会不会互相覆盖？
    后面为什么有些头文件找不到？
替换标准C语言实现的库函数如果需要引用头文件，顺序是否受影响，又应该用什么方式引入？

下面的两个头文件中的相同头文件名称有什么区别与联系？
/usr/lib/gcc/x86_64-linux-gnu/11/include/
/usr/include/**
*/
#include "common.h"

// TODO 所有的头文件的介绍，包含引入标准的时间，主要功能？？在哪里能查到
/* C89标准库 提供了 assert 的宏,它可用于验证程序做出的假设,并在假设为假时输出诊断消息 */
#include <assert.h>

/* C99标准库 布尔类型定义 */
#include <stdbool.h>

/*
操作系统相关，是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称
可移植操作系统
*/
#include <unistd.h>

/*
TODO 非标准的头文件，来自于哪个库，怎么安装 的？
FFmpeg中用于各种音视频封装格式的生成和解析，
    包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能，
    包含demuxers和muxer库
    // TODO 需要怎么安装一下这个库？
*/
#include <libavformat/avformat.h>

// HAVE_V4L2 是在根目录 meson_option.txt 中定义，然后自动生成在config.h中
#ifdef HAVE_V4L2

/*format: 指明像素格式，可以是YUV，也可以是RGB
access: 指明Texture的类型。可以是 Stream(视频)，也可以是Target一般的类型。
 * HAVE_V4L2 是在 config.h 中定义的，由meson根据命令配置生成
 * V4L2（Video4Linux的缩写）是Linux下关于视频采集相关设备的驱动框架，为驱动和应用程序提供了一套统一的接口规范。
 * V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动
*/
# include <libavdevice/avdevice.h>
#endif

/*
SDL_MAIN_HANDLED 在根目录下的meson.build中定义
/usr/include/SDL2/SDL_main.h 中有关于此宏定义的说明，Windows平台下编译时可能会出错，定义此宏规避编译错误
*/
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem

/*
SDL（Simple DirectMedia Layer）是一套开源的跨平台多媒体开发库，使用 C 语言写成。
它提供了绘制图像、播放声音、获取键盘输入等相关的 API，
大大降低多媒体应用开发难度的同时，也让开发者只要用相同或是相似的代码就可以开发出跨多个平台（Linux、Windows、Mac OS X等）的应用软件。
多用于开发游戏、模拟器、媒体播放器等多媒体应用领域
*/
#include <SDL2/SDL.h>

#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
#include "version.h"

#ifdef _WIN32
#include <windows.h>
#include "util/str.h"
#endif

#include "util/strbuf.h"

/**
 * 参数解析后调用后续的处理过程
 */
static int
main_scrcpy(int argc, char *argv[]) {
#ifdef _WIN32
    // disable buffering, we want logs immediately
    // even line buffering (setvbuf() with mode _IOLBF) is not sufficient

    /*
    setbuf C标准库函数，主要用于打开和关闭缓冲机制。下面的语句是关闭缓冲，有需要打印的内容立即打印出来
    TODO setbuf与setvbuf的比较
    */
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
#else
    // 此处只是打印日志的一个示例，表示可以打印汉字.其实此处还没有进行日志系统的初始化
    JACK_INFO_PRINT();
    LOGI("程序启动：这是 <非windows系统> 命令行的输出测试。\n");
#endif

/*
assert.h中使用到，专门用来控制assert()的行为。如果定义了 NDEBUG 这个宏，则assert不会起作用。
用编码的方式测试下面的宏定义,正式编译将自动定义 NDEBUG ,NOT DEBUG
*/
#ifdef NDEBUG
    printf("main - NDEBUG is defined !\n");
#else
    // printf方式也是可以直接输出内容的
    printf("main - NDEBUG is not defined !\n");
#endif

#ifndef NDEBUG
    JACK_INFO_PRINT();
    LOGI("sc_thread_get_id(): %u .  this is jack build,thanks for use!\n", sc_thread_get_id());
    LOGI("getpid(): %u . different from the sc_thread_get_id()  \n  ", getpid());
#endif

    // 测试输出错误日志,debug编译输出到了命令行中
    JACK_INFO_PRINT();
    LOGE("This is jack test for error,ingore this message\n");

    /*
    宏 SCRCPY VERSION 定义在config.h中，来自于本目录下meson.build中的配置
    TODO printf相关的打印函数的用法
    */
    printf("scrcpy " SCRCPY_VERSION
           " <https://github.com/Genymobile/scrcpy>\n");

    // TODO 结构体定义完，各个不同类型的字段的默认值是什么样的？验证一下

    // 运行时的参数
    struct scrcpy_cli_args args = {
        .opts = scrcpy_options_default,
        .help = false,
        .version = false,
        .pause_on_exit = SC_PAUSE_ON_EXIT_FALSE,
    };

    // meson.build中有定义的处理
#ifndef NDEBUG
    args.opts.log_level = SC_LOG_LEVEL_DEBUG;
#endif

#ifndef NDEBUG
    // 输出执行命令输入的参数
    // 有处理这个动态数组的代码，找找看 argv_to_string(
    struct sc_strbuf buf;
    sc_strbuf_init(&buf, 512);

    JACK_INFO_PRINT();
    LOGI("命令行入参情况说明:\n argc ( argument count ) = %d \n", argc);
    int i;
    for(i = 0; i < argc; ++i){
        sc_strbuf_append_str(&buf,argv[i]);
        sc_strbuf_append_char(&buf,' ');
    }

    LOGW("%s\n",buf.s);
    free(buf.s);
    LOGI("\n命令行入参打印完成\n");
#endif

    enum scrcpy_exit_code ret;

    // 测试见 test_cli
    if (!scrcpy_parse_args(&args, argc, argv)) {
        ret = SCRCPY_EXIT_FAILURE;
        goto end;
    }

    sc_set_log_level(args.opts.log_level);

    if (args.help) {
        scrcpy_print_usage(argv[0]);
        ret = SCRCPY_EXIT_SUCCESS;
        goto end;
    }

    if (args.version) {
        scrcpy_print_version();
        ret = SCRCPY_EXIT_SUCCESS;
        goto end;
    }

    // The current thread is the main thread
    SC_MAIN_THREAD_ID = sc_thread_get_id();

#ifndef NDEBUG
    JACK_INFO_PRINT();
    LOGI("sc_thread_get_id(): %u", SC_MAIN_THREAD_ID);
#endif

    /*
    compat.h中定义了下面的宏
    scrcpy_lavf_requires_register_all
     */
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
    /*
     FFMPEG:av_register_all()是所有基于ffmpeg的应用程序中第一个被调用的函数,
     只有调用了该函数，才能正常使用ffmpeg的各项功能, 如复用/解复用器，编码/解码器, 以及各种协议等等.
     av_register_all()的声明位于libavformat/avformat.h头文件中，在新版本4.0以后，不需要调用该方法，可以直接使用所有模块
     https://blog.csdn.net/u011686167/article/details/121691245
     */
    av_register_all();
#endif

#ifdef HAVE_V4L2
    if (args.opts.v4l2_device) {
        /*
        FFmpeg的avdevice_register_all()用于注册输入/输出设备，属于libavdevice模块，是libavformat模块的补充，
        包含muxers与demuxers两部分。
        设备与平台相关，比如Android、iOS、Windows、Mac、Linux等不同平台提供不同设备。
        */
        avdevice_register_all();
    }
#endif

    if (!net_init()) {
        ret = SCRCPY_EXIT_FAILURE;
        goto end;
    }

    sc_log_configure();

/* HAVE_USB 在项目根目录 meson_option.txt 中定义 */
#ifdef HAVE_USB
    ret = args.opts.otg ? scrcpy_otg(&args.opts) : scrcpy(&args.opts);
#else
    ret = scrcpy(&args.opts);
#endif

end:
    if (args.pause_on_exit == SC_PAUSE_ON_EXIT_TRUE ||
            (args.pause_on_exit == SC_PAUSE_ON_EXIT_IF_ERROR &&
                ret != SCRCPY_EXIT_SUCCESS)) {
        printf("Press Enter to continue...\n");
        getchar();
    }

    return ret;
}

/**
 * scrcpy主函数，代码从这里开始运行，正常退出不出错时，最后也是从这个函数退出系统。
 * 全文搜索 abort(); _exit() 可看到其他的异常退出的情况
 * @param {int} argc 命令行传入的参数的数量
 * @param argv 命令行传入的参数的字符串指针的数组，
 *  数组的大小由argc指定。第一个参数是执行的命令，也就是可执行文件的绝对或相对路径
 * @return 成功返回 0,失败返回 非零
 * @see scrcpy_exit_code （全文搜索即可）
 * @see {test_system.c}
 * TODO  char *argv[] 与 定义 const char *const *argv 的区别与联系
 */
int
main(int argc, char *argv[]) {

    // _WIN32 windows中内部定义的，标志着当前编译环境是windows环境
    // 从前的版本中 关于windows的宏定义比较混乱，有用__WIN32的，官方版本已经逐步开始统一
    /*
    @see {test_system.c}
    */
#ifndef _WIN32
    return main_scrcpy(argc, argv);
#else

    // Windows系统下初始处理逻辑，将Windows下的参数输入修改成Linux兼容的格式
    (void) argc;
    (void) argv;
    int wargc;
    wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
    if (!wargv) {
        LOG_OOM();
        return SCRCPY_EXIT_FAILURE;
    }

    char **argv_utf8 = malloc((wargc + 1) * sizeof(*argv_utf8));
    if (!argv_utf8) {
        LOG_OOM();
        LocalFree(wargv);
        return SCRCPY_EXIT_FAILURE;
    }

    argv_utf8[wargc] = NULL;

    for (int i = 0; i < wargc; ++i) {
        argv_utf8[i] = sc_str_from_wchars(wargv[i]);
        if (!argv_utf8[i]) {
            LOG_OOM();
            for (int j = 0; j < i; ++j) {
                free(argv_utf8[j]);
            }
            LocalFree(wargv);
            free(argv_utf8);
            return SCRCPY_EXIT_FAILURE;
        }
    }

    LocalFree(wargv);

    int ret = main_scrcpy(wargc, argv_utf8);

    for (int i = 0; i < wargc; ++i) {
        free(argv_utf8[i]);
    }
    free(argv_utf8);

    return ret;
#endif
}
